/*
 * Warp!! boot driver  Rev. 5.0.2
 *
 *  Copyright (C) 2015-2016  Lineo Solutions, Inc.
 *
 */

#include "warp_config.h"
#include "warp.h"

#define printf(...)
#define flush_cache(x, y)

#ifndef WARP_PAGE_SIZE
#define WARP_PAGE_SIZE          4096
#endif

#ifdef CONFIG_WARP

#define WARP_DRV_NUM            (sizeof(warp_drv_info) / \
                                 sizeof(struct warp_drv_info))

static const struct warp_drv_info *warp_drv_info;
static int warp_drv_num;

const struct warp_savearea warp_savearea_0[] = {
    WARP_SAVEAREA_0
};

const struct warp_savearea warp_savearea_1[] = {
    WARP_SAVEAREA_1
};

int warp_saveno;

unsigned char *warp_drvaddr;
unsigned char *warp_bfaddr;

unsigned long warp_selector;

static struct warp_boot *boot_param;
static u32 drv_total_size;

#ifdef CONFIG_WARP_NAND

#include "NFC.h"

static u32 warp_nand_skipbad(u32 block, u32 end)
{
    while (block < end) {
        if (nf_check_factory_defect(block, 1))
            return block;
        block++;
    }
    printf("all block is bad\n");
    return (u32)-1;
}

static int warp_nand_load(u32 dev, u64 offs, u32 area_size, void *buf, int size)
{
    int read_pages;
    u32 pages = ((size + nfc_dev_inf_tbl.main_area_size - 1) /
                 nfc_dev_inf_tbl.main_area_size);
    u32 block = offs / nfc_dev_inf_tbl.block_size;
    u32 end = block + area_size / nfc_dev_inf_tbl.block_size;

    while (pages > 0) {
        block = warp_nand_skipbad(block, end);
        if (block == (u32)-1)
            return 1;

        if (pages >= nfc_dev_inf_tbl.page_per_block)
            read_pages = nfc_dev_inf_tbl.page_per_block;
        else
            read_pages = pages;
        if (nf_bread(block, 0, read_pages, buf) != 0) {
            printf("nand read error\n");
            return 1;
        }
        pages -= read_pages;
        buf += read_pages * nfc_dev_inf_tbl.main_area_size;
    }

    return 0;
}

#endif  /* CONFIG_WARP_NAND */

#ifdef CONFIG_WARP_SD

#include "em_if_ext.h"
#include "em_int_cmn.h"
#include "em_config.h"

static u32 current_dev;
#ifdef CONFIG_NEED_INITIALIZE
static int warp_sd_init(u32 dev)
{
    int ret;
    u32 part;

    if (((current_dev ^ dev) & ~WARP_PART_MASK)) {
        current_dev = (u32)(-1);

        if ((ret = MWEM_Init()) != EM_OK)
            return 1;
        if ((ret = MWEM_DeviceInit()) != EM_OK)
            return 1;
    }

    part = WARP_DEV_TO_PART(dev);
    if (WARP_DEV_TO_PART(current_dev) != part) {
        if ((ret = MWEM_Set_AccessPartition(part)) != 0)
            return 1;
    }

    current_dev = dev;
    return 0;
}
#endif
static int warp_sd_load(u32 dev, u64 offs, void *buf, int size)
{
    int ret, blk;
#ifdef CONFIG_NEED_INITIALIZE
    if ((ret = warp_sd_init(dev)) != 0)
        return ret;
#endif
    blk = (size + 512 - 1) / 512;
    if ((ret = MWEM_DirectRead(offs, blk, buf)) >= 0)
        return 0;

    return 1;
}

#endif  /* CONFIG_WARP_SD */

static int warp_load_drv(int no, void *buf, u32 size)
{
    int ret = 1;
    const struct warp_drv_info *drv = &warp_drv_info[no];
    u32 dev = drv->drv.dev & WARP_DEV_MASK;

    if (0) {
#ifdef CONFIG_WARP_NAND
    } else if (dev == WARP_DEV_NAND) {
        ret = warp_nand_load(drv->drv.dev, drv->drv.offs, drv->drv.size,
                             buf, size);
#endif
#ifdef CONFIG_WARP_SD
    } else if (dev == WARP_DEV_SD) {
        ret = warp_sd_load(drv->drv.dev, drv->drv.offs, buf, size);
#endif
#ifdef CONFIG_WARP_EXT
    } else if (dev == WARP_DEV_EXT) {
        ret = warp_extdrv_load(drv->drv.dev, drv->drv.offs, drv->drv.size,
                               buf, size);
#endif
    }

    if (ret != 0)	//pgr0002
        printf("Can't load Warp!! driver %d.\n", no);

    return ret;
}

static int _warp_drvload(int fixed_only)
{
    int ret = 0;
    int no, size, sum = 0;
    int id = WARP_ID_DRIVER;
    void *header;
    unsigned char *floating_buf;

    if (warp_drv_info[0].mode == WARP_DRV_FIXED)
        warp_drvaddr = (void *)warp_drv_info[0].addr;
    else
        warp_drvaddr = (void *)CONFIG_WARP_LOAD_ADDR;
    floating_buf = warp_drvaddr;

    for (no = 0; no < warp_drv_num; no++) {
        const struct warp_drv_info *drv = &warp_drv_info[no];
        if (drv->mode == WARP_DRV_FLOATING) {
            if (fixed_only)
                continue;
            header = floating_buf;
        } else if (drv->mode == WARP_DRV_FIXED) {
            header = (void *)drv->addr;
        } else {
            continue;
        }

        if (fixed_only || drv->mode == WARP_DRV_FLOATING) {
            if (warp_load_drv(no, header, 32) != 0)
                return 1;
            if (WARP_DRV_ID(header) != id) {
                if (id == WARP_ID_DRIVER) {
                    return 1;
                } else {
                    printf("Can't find UserAPI driver %d\n", no - 1);
                    continue;
                }
            }
            size = WARP_DRV_DATA_END(header);
            if (warp_load_drv(no, header, size) != 0)
                return 1;

            flush_cache((unsigned long)header, size);
        }

        if (drv->mode == WARP_DRV_FLOATING || no == 0) {
            size = (WARP_DRV_DATA_END(header) + WARP_PAGE_SIZE - 1) &
                ~(WARP_PAGE_SIZE - 1);
            floating_buf += size;
            sum += size;
        }

        id = WARP_ID_USER_API;
    }

    drv_total_size = sum;
    return ret;
}

int warp_drvload(void)
{
    return _warp_drvload(1);
}

static int warp_bfload(int saveno, void *bfaddr)
{
    int ret = 1;
    const struct warp_savearea *sa;
    u32 dev;

	if (warp_selector == 0) {
		sa = &warp_savearea_0[saveno];
	}
	else if (warp_selector == 1) {
		sa = &warp_savearea_1[saveno];
	}
	else {
		return 1;
	}

	dev = sa->bootflag.dev & WARP_DEV_MASK;

    if (0) {
#ifdef CONFIG_WARP_NAND
    } else if (dev == WARP_DEV_NAND) {
        ret = warp_nand_load(sa->bootflag.dev, sa->bootflag.offs,
                             sa->bootflag.size, bfaddr, WARP_BF_LEN);
#endif
#ifdef CONFIG_WARP_SD
    } else if (dev == WARP_DEV_SD) {
        ret = warp_sd_load(sa->bootflag.dev, sa->bootflag.offs,
                           bfaddr, WARP_BF_LEN);
            return ret;
#endif
#ifdef CONFIG_WARP_EXTDRV
    } else if (dev == WARP_DEV_EXT) {
        ret = warp_extdrv_load(sa->bootflag.dev, sa->bootflag.offs,
                               sa->bootflag.size, bfaddr, WARP_BF_LEN);
#endif
#ifdef CONFIG_WARP_USERDRV
    } else if (dev == WARP_DEV_USER) {
        ret = warp_userdrv_load(sa->bootflag.dev, sa->bootflag.offs,
                                sa->bootflag.size, bfaddr, WARP_BF_LEN);
#endif
    }

    return ret;
}

int warp_checkboot(int saveno)
{
    int ret;

    if (saveno < 0)
        saveno = warp_saveno;
    if (saveno < 0 || saveno >= WARP_SAVEAREA_NUM) {	//pgr1223
        printf("Illegal saveno %d\n", saveno);
        return 1;
    }

    if ((ret = _warp_drvload(0)) != 0)
        return ret;

    warp_bfaddr = (warp_drvaddr + WARP_DRV_TEXT_END(warp_drvaddr));

    if ((ret = warp_bfload(saveno, warp_bfaddr)) != 0)
        return ret;

    if (*(unsigned int *)warp_bfaddr != WARP_ID_BOOTFLAG)
        return 1;

    boot_param = (void *)(warp_bfaddr + WARP_BF_LEN);

    return 0;
}

int warp_load(int saveno)
{
    int i, ret;
    u32 *p;

    if ((ret = warp_checkboot(saveno)) != 0)
        return ret;

    p = (u32 *)boot_param;
    for (i = 0; i < sizeof(*boot_param) / sizeof(u32); i++)
        *p++ = 0;
    boot_param->silent = -1;
    boot_param->console = -1;
    boot_param->bps = -1;
    boot_param->drv_total_size = drv_total_size;

    return 0;
}

int warp_boot(int saveno)
{
    int ret;
    int (*hibernate)(void);

    if ((ret = warp_load(saveno)) != 0)
        return ret;

    hibernate = (int (*)(void))(u32)(warp_drvaddr + WARP_HEADER_HIBERNATE);
    ret = hibernate();
    printf("Warp!! error can not boot %d\n", ret);
    return ret;
}

void warp_init(const struct warp_drv_info *drvinfo, int drvnum, int saveno, unsigned long selector)
{
    warp_drv_info = drvinfo;
    warp_drv_num = drvnum;
    warp_saveno = saveno;
	warp_selector = selector;

#ifdef CONFIG_WARP_SD
    current_dev = 0;
#endif
}

#endif  /* CONFIG_WARP */
